package com.auditlog.format;

import cn.hutool.core.collection.CollectionUtil;
import com.auditlog.converter.ConverterRegistry;
import com.auditlog.datasource.db.SqlType;
import com.auditlog.sql.runner.DefaultSqlRunner;
import com.auditlog.datasource.struct.FieldChangeLog;
import com.auditlog.datasource.struct.RecordImage;
import com.auditlog.datasource.struct.RowChangeLog;
import com.auditlog.datasource.struct.TableRecord;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@Slf4j
public abstract class AbstractRecordImageFormat implements RecordImageFormat {

    public static final DefaultSqlRunner DEFAULT_SQL_RUNNER = new DefaultSqlRunner(null, null);

    @Override
    public void format(RecordImage recordImage) {
        if (recordImage == null) {
            return;
        }
        doFormat(recordImage);
    }

    @Override
    public Map<SqlType, List<RowChangeLog>> getChangeLog(RecordImage recordImage) throws Exception {
        TableRecord afterRecord = recordImage.getAfterRecord();
        TableRecord beforeRecord = recordImage.getBeforeRecord();
        if ((beforeRecord == null || beforeRecord.getAffectedRows() == 0)
                && (afterRecord == null || afterRecord.getAffectedRows() == 0)) {
            return new HashMap<>(1);
        }
        Map<SqlType, List<RowChangeLog>> sqlTypeListMap = changeLog(recordImage);

        Set<SqlType> removeKeys = new HashSet<>();
        sqlTypeListMap.forEach((k, v) -> {
            if (CollectionUtil.isEmpty(v)) {
                removeKeys.add(k);
            }
        });
        removeKeys.forEach(k -> sqlTypeListMap.remove(k));
        return sqlTypeListMap;
    }

    /**
     * 获取插入数据的日志
     *
     * @param recordImage
     * @return: java.util.Map<com.auditlog.datasource.db.SqlType, java.util.List < java.util.List < com.auditlog.datasource.struct.FieldChangeLog>>>
     */
    public Map<SqlType, List<RowChangeLog>> buildInsertChangeLog(RecordImage recordImage) throws Exception {
        List<List<Object>> insertPks = recordImage.getInsertPks();
        List<String> pkColumnsName = recordImage.getPkColumnsName();
        TableRecord afterRecord = recordImage.getAfterRecord();
        String delimiter = afterRecord.getDelimiter();
        List<RowChangeLog> insertRowChangeLogList = new ArrayList<>();
        Map<SqlType, List<RowChangeLog>> result = new HashMap<>();
        if (CollectionUtil.isNotEmpty(insertPks)) {
            for (List<Object> insertPk : insertPks) {
                String key = DEFAULT_SQL_RUNNER.assembleKey(pkColumnsName, insertPk, delimiter);
                List<Object> row = afterRecord.getRows().get(key);
                if (row == null) {
                    throw new IllegalStateException(String.format("表[%s]更新后的数据中未找到[key:%s]对应的数据", recordImage.getTableName(), key));
                }
                List<FieldChangeLog> fieldChangeLogs = buildChangeLog(null, row, recordImage.getTableMeta().getColumnNamesByOrder());
                Map<String, String> keyMap = this.getKeyMap(recordImage.getTableMeta().getPrimaryKeyWithAlias("", false), insertPk);
                insertRowChangeLogList.add(RowChangeLog.builder().fieldChangeLogs(fieldChangeLogs).keyMap(keyMap).build());
            }
            if (insertRowChangeLogList.size() > 0) {
                result.put(SqlType.INSERT, insertRowChangeLogList);
            }
        }
        return result;
    }

    /**
     * 获取更新数据的日志
     *
     * @param recordImage
     * @return: java.util.Map<com.auditlog.datasource.db.SqlType, java.util.List < java.util.List < com.auditlog.datasource.struct.FieldChangeLog>>>
     */
    public Map<SqlType, List<RowChangeLog>> buildUpdateChangeLog(RecordImage recordImage) throws Exception {
        List<List<Object>> insertPks = recordImage.getInsertPks();
        List<String> pkColumnsName = recordImage.getPkColumnsName();
        TableRecord afterRecord = recordImage.getAfterRecord();
        TableRecord beforeRecord = recordImage.getBeforeRecord();
        String delimiter = afterRecord.getDelimiter();
        List<RowChangeLog> updateRowChangeLogList = new ArrayList<>();
        Map<SqlType, List<RowChangeLog>> result = new HashMap<>();
        List<List<Object>> possibleUpdateOrDeletePks = recordImage.getPossibleUpdateOrDeletePks();
        if (CollectionUtil.isNotEmpty(possibleUpdateOrDeletePks)) {
            for (List<Object> possibleUpdateOrDeletePk : possibleUpdateOrDeletePks) {
                String key = DEFAULT_SQL_RUNNER.assembleKey(pkColumnsName, possibleUpdateOrDeletePk, delimiter);
                List<Object> afterRow = afterRecord.getRows().get(key);
                List<Object> beforeRow = beforeRecord.getRows().get(key);
                if (afterRow == null || beforeRow == null) {
                    throw new IllegalStateException(String.format("表[%s]更新数据[%s]状态不正确", recordImage.getTableName(), key));
                }
                List<FieldChangeLog> fieldChangeLogs = buildChangeLog(beforeRow, afterRow, recordImage.getTableMeta().getColumnNamesByOrder());
                Map<String, String> keyMap = this.getKeyMap(recordImage.getTableMeta().getPrimaryKeyWithAlias("", false), possibleUpdateOrDeletePk);
                updateRowChangeLogList.add(RowChangeLog.builder().fieldChangeLogs(fieldChangeLogs).keyMap(keyMap).build());
            }
            if (updateRowChangeLogList.size() > 0) {
                result.put(SqlType.UPDATE, updateRowChangeLogList);
            }
        }
        return result;
    }

    /**
     * 获取删除数据的日志
     *
     * @param recordImage
     * @return: java.util.Map<com.auditlog.datasource.db.SqlType, java.util.List < java.util.List < com.auditlog.datasource.struct.FieldChangeLog>>>
     */
    public Map<SqlType, List<RowChangeLog>> buildDeleteChangeLog(RecordImage recordImage) throws Exception {
        TableRecord beforeRecord = recordImage.getBeforeRecord();
        List<String> pkColumnsName = recordImage.getPkColumnsName();
        String delimiter = beforeRecord.getDelimiter();
        List<RowChangeLog> deleteRowChangeLogList = new ArrayList<>();
        Map<SqlType, List<RowChangeLog>> result = new HashMap<>();
        List<List<Object>> possibleUpdateOrDeletePks = recordImage.getPossibleUpdateOrDeletePks();
        if (CollectionUtil.isNotEmpty(possibleUpdateOrDeletePks)) {
            for (List<Object> possibleUpdateOrDeletePk : possibleUpdateOrDeletePks) {
                String key = DEFAULT_SQL_RUNNER.assembleKey(pkColumnsName, possibleUpdateOrDeletePk, delimiter);
                List<Object> row = beforeRecord.getRows().get(key);
                if (row == null) {
                    throw new IllegalStateException(String.format("表[%s]未找到被删除数据[%s]对应的数据", recordImage.getTableName(), key));
                }
                List<FieldChangeLog> fieldChangeLogs = this.buildChangeLog(row, null, recordImage.getTableMeta().getColumnNamesByOrder());
                Map<String, String> keyMap = this.getKeyMap(recordImage.getTableMeta().getPrimaryKeyWithAlias("", false), possibleUpdateOrDeletePk);
                deleteRowChangeLogList.add(RowChangeLog.builder().fieldChangeLogs(fieldChangeLogs).keyMap(keyMap).build());
            }
        }
        if (deleteRowChangeLogList.size() > 0) {
            result.put(SqlType.DELETE, deleteRowChangeLogList);
        }
        return result;
    }

    /**
     * 获取变更字段
     *
     * @param oldRow      变更前
     * @param newRow      变更后
     * @param columnNames 列名集合
     * @return: java.util.List<com.auditlog.datasource.struct.FieldChangeLog>
     */
    public List<FieldChangeLog> buildChangeLog(List<Object> oldRow, List<Object> newRow, List<String> columnNames) throws Exception {
        List<FieldChangeLog> fieldChangeLogs = new ArrayList<>();
        // 插入
        if (CollectionUtil.isEmpty(oldRow) && CollectionUtil.isNotEmpty(newRow)) {
            int index = 0;
            for (String columnName : columnNames) {
                Object o = newRow.get(index);
                String newStr = ConverterRegistry.getInstance().convert2Str(o);
                if (newStr != null) {
                    fieldChangeLogs.add(FieldChangeLog.builder().name(columnName).newValue(newStr).oldValue(null).build());
                }
                index++;
            }
        }
        // 删除
        else if (CollectionUtil.isNotEmpty(oldRow) && CollectionUtil.isEmpty(newRow)) {
            int index = 0;
            for (String columnName : columnNames) {
                Object o = oldRow.get(index);
                String oldStr = ConverterRegistry.getInstance().convert2Str(o);
                if (oldStr != null) {
                    fieldChangeLogs.add(FieldChangeLog.builder().name(columnName).newValue(null).oldValue(oldStr).build());
                }
                index++;
            }

        }
        // 更新
        else if (CollectionUtil.isNotEmpty(oldRow) && CollectionUtil.isNotEmpty(newRow)) {
            int index = 0;
            for (String columnName : columnNames) {
                Object oldObject = oldRow.get(index);
                Object newObject = newRow.get(index);
                String newStr = ConverterRegistry.getInstance().convert2Str(newObject);
                String oldStr = ConverterRegistry.getInstance().convert2Str(oldObject);
                if (!Objects.equals(newStr, oldStr)) {
                    fieldChangeLogs.add(FieldChangeLog.builder().name(columnName).newValue(newStr).oldValue(oldStr).build());
                }
                index++;
            }
        }
        return fieldChangeLogs;
    }

    public Map<String, String> getKeyMap(List<String> columnNames, List<Object> keyValueList) throws Exception {
        Map<String, String> map = new HashMap<>(columnNames.size(), 1);
        if (CollectionUtil.isNotEmpty(keyValueList)) {
            if (keyValueList.size() != columnNames.size()) {
                throw new IllegalStateException("列名与列值数目不一致");
            }
            for (int i = 0; i < keyValueList.size(); i++) {
                Object o = keyValueList.get(i);
                String keyValue = ConverterRegistry.getInstance().convert2Str(o);
                map.put(columnNames.get(i), keyValue);
            }
        }
        return map;
    }

    public void removeKey(List<List<Object>> keys, Set<String> removeKeys, List<String> pkNames, String delimiter) {
        Iterator<List<Object>> iterator = keys.iterator();
        while (iterator.hasNext()) {
            List<Object> pkValue = iterator.next();
            String key = DEFAULT_SQL_RUNNER.assembleKey(pkNames, pkValue, delimiter);
            if (removeKeys.contains(key)) {
                iterator.remove();
            }
        }
    }

    public abstract Map<SqlType, List<RowChangeLog>> changeLog(RecordImage recordImage) throws Exception;

    public abstract void doFormat(RecordImage recordImage);

}
